// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/bluetooth/frame_connected_bluetooth_devices.h"

#include "base/optional.h"
#include "base/strings/string_util.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/web_contents.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"

namespace content {

FrameConnectedBluetoothDevices::FrameConnectedBluetoothDevices(
    RenderFrameHost* rfh)
    : web_contents_impl_(static_cast<WebContentsImpl*>(
        WebContents::FromRenderFrameHost(rfh)))
{
}

FrameConnectedBluetoothDevices::~FrameConnectedBluetoothDevices()
{
    for (size_t i = 0; i < device_id_to_connection_map_.size(); i++) {
        DecrementDevicesConnectedCount();
    }
}

bool FrameConnectedBluetoothDevices::IsConnectedToDeviceWithId(
    const WebBluetoothDeviceId& device_id)
{
    auto connection_iter = device_id_to_connection_map_.find(device_id);
    if (connection_iter == device_id_to_connection_map_.end()) {
        return false;
    }
    // Owners of FrameConnectedBluetoothDevices should notify it when a device
    // disconnects but currently Android and Mac don't notify of disconnection,
    // so the map could get into a state where it's holding a stale connection.
    // For this reason we return the value of IsConnected for the connection.
    // TODO(ortuno): Always return true once Android and Mac notify of
    // disconnection.
    // http://crbug.com/607273
    return connection_iter->second->IsConnected();
}

void FrameConnectedBluetoothDevices::Insert(
    const WebBluetoothDeviceId& device_id,
    std::unique_ptr<device::BluetoothGattConnection> connection)
{
    auto connection_iter = device_id_to_connection_map_.find(device_id);
    if (connection_iter != device_id_to_connection_map_.end()) {
        // Owners of FrameConnectedBluetoothDevices should notify it when a device
        // disconnects but currently Android and Mac don't notify of disconnection,
        // so the map could get into a state where it's holding a stale connection.
        // For this reason we check if the current connection is active and if
        // not we remove it.
        // TODO(ortuno): Remove once Android and Mac notify of disconnection.
        // http://crbug.com/607273
        if (!connection_iter->second->IsConnected()) {
            device_address_to_id_map_.erase(
                connection_iter->second->GetDeviceAddress());
            device_id_to_connection_map_.erase(connection_iter);
            DecrementDevicesConnectedCount();
        } else {
            // It's possible for WebBluetoothServiceImpl to issue two successive
            // connection requests for which it would get two successive responses
            // and consequently try to insert two BluetoothGattConnections for the
            // same device. WebBluetoothServiceImpl should reject or queue connection
            // requests if there is a pending connection already, but the platform
            // abstraction doesn't currently support checking for pending connections.
            // TODO(ortuno): CHECK that this never happens once the platform
            // abstraction allows to check for pending connections.
            // http://crbug.com/583544
            return;
        }
    }
    device_address_to_id_map_[connection->GetDeviceAddress()] = device_id;
    device_id_to_connection_map_[device_id] = std::move(connection);
    IncrementDevicesConnectedCount();
}

void FrameConnectedBluetoothDevices::CloseConnectionToDeviceWithId(
    const WebBluetoothDeviceId& device_id)
{
    auto connection_iter = device_id_to_connection_map_.find(device_id);
    if (connection_iter == device_id_to_connection_map_.end()) {
        return;
    }
    CHECK(device_address_to_id_map_.erase(
        connection_iter->second->GetDeviceAddress()));
    device_id_to_connection_map_.erase(connection_iter);
    DecrementDevicesConnectedCount();
}

base::Optional<WebBluetoothDeviceId>
FrameConnectedBluetoothDevices::CloseConnectionToDeviceWithAddress(
    const std::string& device_address)
{
    auto device_address_iter = device_address_to_id_map_.find(device_address);
    if (device_address_iter == device_address_to_id_map_.end()) {
        return base::nullopt;
    }
    WebBluetoothDeviceId device_id = device_address_iter->second;
    CHECK(device_address_to_id_map_.erase(device_address));
    CHECK(device_id_to_connection_map_.erase(device_id));
    DecrementDevicesConnectedCount();
    return base::make_optional(device_id);
}

void FrameConnectedBluetoothDevices::IncrementDevicesConnectedCount()
{
    web_contents_impl_->IncrementBluetoothConnectedDeviceCount();
}

void FrameConnectedBluetoothDevices::DecrementDevicesConnectedCount()
{
    web_contents_impl_->DecrementBluetoothConnectedDeviceCount();
}

} // namespace content
